package sql_old

import (
	"database/sql"
	"database/sql/driver"
	"encoding/json"
	"errors"
	"fmt"


	"github.com/ingopansa/userinfoservice/storage"
	"github.com/sirupsen/logrus"
	"github.com/ingopansa/userinfoservice/model"
	)

// TODO(ericchiang): The update, insert, and select methods queries are all
// very repetitive. Consider creating them programmatically.

// keysRowID is the ID of the only row we expect to populate the "keys" table.
const keysRowID = "keys"

// encoder wraps the underlying value in a JSON marshaler which is automatically
// called by the database/sql package.
//
//		s := []string{"planes", "bears"}
//		err := db.Exec(`insert into t1 (id, things) values (1, $1)`, encoder(s))
//		if err != nil {
//			// handle error
//		}
//
//		var r []byte
//		err = db.QueryRow(`select things from t1 where id = 1;`).Scan(&r)
//		if err != nil {
//			// handle error
//		}
//		fmt.Printf("%s\n", r) // ["planes","bears"]
//
func encoder(i interface{}) driver.Valuer {
	return jsonEncoder{i}
}

// decoder wraps the underlying value in a JSON unmarshaler which can then be passed
// to a database Scan() method.
func decoder(i interface{}) sql.Scanner {
	return jsonDecoder{i}
}

type jsonEncoder struct {
	i interface{}
}

func (j jsonEncoder) Value() (driver.Value, error) {
	b, err := json.Marshal(j.i)
	if err != nil {
		return nil, fmt.Errorf("marshal: %v", err)
	}
	return b, nil
}

type jsonDecoder struct {
	i interface{}
}

func (j jsonDecoder) Scan(dest interface{}) error {
	if dest == nil {
		return errors.New("nil value")
	}
	b, ok := dest.([]byte)
	if !ok {
		return fmt.Errorf("expected []byte got %T", dest)
	}
	if err := json.Unmarshal(b, &j.i); err != nil {
		return fmt.Errorf("unmarshal: %v", err)
	}
	return nil
}

// Abstract conn vs trans.
type querier interface {
	QueryRow(query string, args ...interface{}) *sql.Row
}

// Abstract row vs rows.
type scanner interface {
	Scan(dest ...interface{}) error
}

func (c *conn) UpdateKeys(updater func(old storage.Keys) (storage.Keys, error)) error {
	return c.ExecTx(func(tx *trans) error {
		firstUpdate := false
		// TODO(ericchiang): errors may cause a transaction be rolled back by the SQL
		// server. Test this, and consider adding a COUNT() command beforehand.
		old, err := getKeys(tx)
		if err != nil {
			if err != storage.ErrNotFound {
				return fmt.Errorf("get keys: %v", err)
			}
			firstUpdate = true
			old = storage.Keys{}
		}

		nk, err := updater(old)
		if err != nil {
			return err
		}

		if firstUpdate {
			_, err = tx.Exec(`
				insert into keys (
					id, verification_keys, signing_key, signing_key_pub, next_rotation
				)
				values ($1, $2, $3, $4, $5);
			`,
				keysRowID, encoder(nk.VerificationKeys), encoder(nk.SigningKey),
				encoder(nk.SigningKeyPub), nk.NextRotation,
			)
			if err != nil {
				return fmt.Errorf("insert: %v", err)
			}
		} else {
			_, err = tx.Exec(`
				update keys
				set 
				    verification_keys = $1,
					signing_key = $2,
					signing_key_pub = $3,
					next_rotation = $4
				where id = $5;
			`,
				encoder(nk.VerificationKeys), encoder(nk.SigningKey),
				encoder(nk.SigningKeyPub), nk.NextRotation, keysRowID,
			)
			if err != nil {
				return fmt.Errorf("update: %v", err)
			}
		}
		return nil
	})
}

func (c *conn) GetKeys() (keys storage.Keys, err error) {
	return getKeys(c)
}

func getKeys(q querier) (keys storage.Keys, err error) {
	err = q.QueryRow(`
		select
			verification_keys, signing_key, signing_key_pub, next_rotation
		from keys
		where id=$1
	`, keysRowID).Scan(
		decoder(&keys.VerificationKeys), decoder(&keys.SigningKey),
		decoder(&keys.SigningKeyPub), &keys.NextRotation,
	)
	if err != nil {
		if err == sql.ErrNoRows {
			return keys, storage.ErrNotFound
		}
		return keys, fmt.Errorf("query keys: %v", err)
	}
	return keys, nil
}


func (c *conn) ListUsers()(users []model.User, err error){
	fmt.Printf( "sql.ListUsers()\n")
	rows, err := c.Query(`
		select
			id
		from users;
	`)
	if err != nil {
		return nil, fmt.Errorf("query: %v", err)
	}
	for rows.Next() {
		r, err := scanUsers(rows)
		if err != nil {
			return nil, err
		}
		users = append(users, r)
	}
	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("scan: %v", err)
	}
	return users, nil
}

func scanUsers(s scanner) (r model.User, err error) {
	fmt.Printf( "scanUsers()\n" )
	err = s.Scan(
		&r,
	)
	if err != nil {
		if err == sql.ErrNoRows {
			return r, storage.ErrNotFound
		}
		return r, fmt.Errorf("scan users: %v", err)
	}
	fmt.Printf( "scanUsers(): ID=%v\n", r.ID )
	return r, nil
}

func (c *conn) GetUser(ID string)(model.User, error){
	c.logger.Debugf( "sql.GetUser(): ID=%v", ID)
	return getUser( c, c.logger, ID )
}

// getUser returns a user by looking up the id of the user using the sub, then looks at the users table
func getUser(q querier, logger logrus.FieldLogger, sub string)(user model.User, err error){
	logger.Debugf( "getUser(): sub=%v", sub)
	row := q.QueryRow(`
		select 
			sub
		from users where sub = $1;
	`, sub)

	err = row.Scan(&user.ID, &user.Name, &user.FamilyName, &user.GivenName, &user.Email, &user.EmailVerified, &user.PhoneNumber, &user.Groups,)
	if err != nil {
		logger.Debugf( "error=%v", err )
		if err == sql.ErrNoRows {
			return user, storage.ErrNotFound
		}
		return user, fmt.Errorf("select auth request: %v", err)
	}
	logger.Debugf( "returning user with sub=%v", user.ID )
	return user, nil
}

func (c *conn)AddUser(user model.User)(error){
	//_, err := c.Exec(`
	//	insert into users (
	//		id, subjects, name, givenname, familyname, email, emailverified, phone, groups
	//	)
	//	values (
	//		$1, $2, $3, $4, $5, $6, $7, $8, $9, $10
	//	);
	//`,
	//	user.ID, encoder(user.Subjects), user.Name, user.GivenName, user.FamilyName, user.Email, user.EmailVerified, user.PhoneNumber, encoder(user.Groups),
	//)

	_, err := c.Exec(`
		insert into users (
			name
		)
		values (
			$1
		);
	`,
		user.Name,
	)

	if err != nil {
		if c.alreadyExistsCheck(err) {
			return storage.ErrAlreadyExists
		}
		return errors.New(fmt.Sprintf("insert auth request: %v", err))
	}
	return nil
}
